<?php
/**
 * Storage adapter for Db_Object
 * @package Db
 * @subpackage Db_Object
 * @author Kirill A Egorov kirill.a.egorov@gmail.com 
 * @copyright Copyright (C) 2011-2012  Kirill A Egorov, 
 * DVelum project http://code.google.com/p/dvelum/ , http://dvelum.net
 * @license General Public License version 3
 */
class Db_Object_Store
{
    protected static $_instance =  null;
    /**
     * @var Db_Object_Event_Manager (optional)
     */
    protected static $_eventManager = null;
	/**
     * @return Db_Object_Store
     */
	static public function getInstance(){
        if(is_null(self::$_instance))	
           self::$_instance = new self;     
        return self::$_instance;   
    }
    /**
     *  Database connector
     * @var Zend_Db_Adapter_Abstract
     */
    protected $_db;
    
    protected function __construct()
    {
        $db = Store_Local::getInstance()->get('db');
        
        if(!($db instanceof Zend_Db_Adapter_Abstract))
            throw new Exception('Can not find DB Adapter');
            
        $this->_db = $db;    
    }  
    /**
     * Set event nanager
     * @param Db_Object_Event_Manager $obj
     */
    static public function setEventManager(Db_Object_Event_Manager $obj){
    	self::$_eventManager = $obj;
    }   
    /**
     * Update Db object
     * @param Db_Object $object
     * @param boolean $log - optional, log changes
     * @param boolen $transaction - optional, use transaction if available
     * @return boolean
     */
    public function update(Db_Object $object , $log = true ,$transaction = true)
    {  	
    	/*
    	 * Check object id
    	 */
    	if(!$object->getId())
            return false;

        /*
         * Check for updates
         */    
        if(!$object->hasUpdates())
            return $object->getId();

        /*
         * Fire "BEFORE_UPDATE" Event if event manager exists
         */    
        if(self::$_eventManager)
            self::$_eventManager->fireEvent(Db_Object_Event_Manager::BEFORE_UPDATE, $object);    
                
        /*
         * Validate unique values
         */  
         $values = $object->validateUniqueValues();
	     
         if(!empty($values))
	        return false;   

	     /*
	      * Check if DB table support transactions
	      */
         $transact = $object->getConfig()->isTransact();
    	
	     if($transact && $transaction)
	    	 $this->_db->beginTransaction();
	    		
	     if(!$this->_updateOperation($object))
	     {
	        if($transact && $transaction)
	        	$this->_db->rollBack();
	        	
	        return false;	
	     }             
         /*
          * Save history if required
          */      
	     if($log && $object->getConfig()->get('save_history'))
	     {            
                Model_Historylog::getInstance()->log(
                	User::getInstance()->id ,
                	$object->getId() , 
                	Model_Historylog::Update , 
                	$object->getTable()
                );
	     }
         /*
          * Fire "AFTER_UPDATE" Event if event manager exists
          */
         if(self::$_eventManager)
            self::$_eventManager->fireEvent(Db_Object_Event_Manager::AFTER_UPDATE, $object);
            
	     return $object->getId();     
    }
    
    protected function _updateOperation(Db_Object $object)
    {
    	try{
	        $this->_db->update($object->getTable() , $object->serializeLinks($object->getUpdates()) , 'id = '.$object->getId());
	        $this->_updateLinks($object);
	        $object->commitChanges();
	        return true;    
    	}catch (Exception $e){
    		return false;
    	}
    }
    
    protected function _updateLinks(Db_Object $object)
    {
        $updates = $object->getUpdates();

        if(empty($updates))
        	return true;
        	
        foreach ($updates as $k=>$v)
        {
        	$conf = $object->getConfig()->getFieldConfig($k);
            if($object->getConfig()->isMultiLink($k))
            {
            	if(!$this->_clearLinks($object, $k,$conf['link_config']['object']))
                    return false;
                    
                if(!empty($v) && is_array($v))
                    if(!$this->_createLinks($object , $k,$conf['link_config']['object'] , $v))
                       return false;
            }
        }    
        return true;
    }
    /**
     * Remove object multy links
     * @param Db_Object $object
     * @param string $objectField
     * @param string $targetObjectName
     */
    protected function _clearLinks(Db_Object $object ,$objectField , $targetObjectName)
    {       
        $where = 'src = '.$this->_db->quote($object->getName()).'
        		AND
        		 src_id = '.intval($object->getId()).'
        		AND
        		 src_field = '.$this->_db->quote($objectField).' 
                AND
                 target = '.$this->_db->quote($targetObjectName);		
        $linksObj  = new Db_Object('links');     
        try{
            $this->_db->delete($linksObj->getTable(),$where);
            return true;
        } catch (Exception $e){
            return false;
        }
    }
    /**
     * Create links to the object
     * @param Db_Object $object
     * @param string $objectField
     * @param string $targetObjectName
     * @param array $links
     */
    protected function _createLinks(Db_Object $object, $objectField , $targetObjectName , array $links)
    {
        $order = 0;   
        $links = array_keys($links);
        $linksObj  = new Db_Object('links');  
        foreach ($links as $k=>$v)
        {           
            $data = array(
                'src'=>$object->getName(),
                'src_id'=>$object->getId(),
                'src_field'=>$objectField,
                'target'=>$targetObjectName,
                'target_id'=>$v,
                'order'=>$order
            ); 
               
            if(!$this->_db->insert($linksObj->getTable(), $data))
                return false;
                 
            $order++;
        }
        return true;
    }
    /**
     * Insert Db object
     * @param Db_Object $object
     * @param boolen $log - optional, log changes
     * @param boolean $transaction - optional , use transaction if available
     * @return integer -  inserted id
     */
    public function insert(Db_Object $object , $log = true , $transaction = true)
    {   
    	if(self::$_eventManager)
            self::$_eventManager->fireEvent(Db_Object_Event_Manager::BEFORE_ADD, $object); 
       /*
	    * Check if DB table support transactions
	    */    	
    	$transact = $object->getConfig()->isTransact();
    	
    	if($transact && $transaction)
    		$this->_db->beginTransaction();
    	   	
    	$success = $this->_insertOperation($object);	
    	
        if(!$success)
        {
        	if($transact && $transaction)
        		$this->_db->rollBack();
        	return false;	
        } 
        else 
        {
        	if($transact)
        		$this->_db->commit();   	
        }        

        if($log &&  $object->getConfig()->get('save_history')){
            Model_Historylog::getInstance()->log(
            	User_Admin::getInstance()->id , 
            	$object->getId() , 
            	Model_Historylog::Create , 
            	$object->getTable()
            );
        }
        
        if(self::$_eventManager)
        	self::$_eventManager->fireEvent(Db_Object_Event_Manager::AFTER_ADD, $object); 
        
        return $object->getId();   
    }

    protected function _insertOperation(Db_Object $object)
    {
    	$updates =  $object->getUpdates();
    	
        if(empty($updates))
            return false;            
        /*
         * Validate unique values
         */
        $values = $object->validateUniqueValues();
        if(!empty($values))
        	return false;
            
    	if(!$this->_db->insert($object->getTable(), $object->serializeLinks($updates)))
             return false;
             
        $id = $this->_db->lastInsertId($object->getTable() , 'id');

        if(!$id)
           return false;  
	             
        $object->setId($id);

        if(!$this->_updateLinks($object))
           return false;    
	               
        $object->commitChanges();
        $object->setId($id);   
        
	    return true;
    }
    
    /**
     * Delete Db object
     * @param Db_Object $object
     * @param boolean $log - optional, log changes
     * @return boolean
     */
    public function delete(Db_Object $object , $log = true)
    {
            if(!$object->getId())
                return false;
                
            if(self::$_eventManager)
            	self::$_eventManager->fireEvent(Db_Object_Event_Manager::BEFORE_DELETE, $object);     
            
            $transact = $object->getConfig()->isTransact();
    	
	    	if($transact)
	    		$this->_db->beginTransaction();
	    		    
            Model_Links::getInstance()->clearObjectLinks($object);
            
            if($this->_db->delete($object->getTable(), 'id =' . $object->getId()))
            {
            	if($log && $object->getConfig()->get('save_history')){
                 	Model_Historylog::getInstance()->log(
                 		User::getInstance()->id , 
                 		$object->getId() , 
                 		Model_Historylog::Delete , 
                 		$object->getTable()
                 	);
            	}
            	$success= true;
            } else{     
                $success = false;
            }   

            if($transact)
            {
                if($success)
                    $this->_db->commit();
                else
                    $this->_db->rollBack();
            }    
                       
            if(self::$_eventManager)
            	self::$_eventManager->fireEvent(Db_Object_Event_Manager::AFTER_DELETE, $object); 
            
            return $success;       
    }
    /**
     * Delete Db object
     * @param string $object
     * @param array $ids
     * @return boolean
     */
    public function deleteObjects($objectName, array $ids)
    {      
        try{        
            $tableName = Db_Object_Config::getInstance($objectName)->getTable();
        }catch(Exception $e){
            return false;
        }
        
    	if(empty($ids))
    		return true;
    	
    	$specialCase = Db_Object::factory($objectName);
	    $where = $this->_db->quoteInto('`id` IN(?)', $ids);
	    	
	    if(self::$_eventManager){
	       	foreach ($ids as $id){
	       		$specialCase->setId($id);
	       		self::$_eventManager->fireEvent(Db_Object_Event_Manager::BEFORE_DELETE, $specialCase); 
	       	}	
	    }
	    	    	
	    if(!$this->_db->delete($tableName, $where))
	    	return false;

	    /*
	     * Clear object liks (links from object)
	     */
	    Model_Links::getInstance()->clearLinksFor($ids);
	    	
        $history = Model_Historylog::getInstance();   
        $userId = User::getInstance()->id;

        /*
         * Save history if required
         */
        if(Db_Object_Config::getInstance($objectName)->get('save_history'))
         	foreach ($ids as $v)
        		$history->log($userId, $v, Model_Historylog::Delete , $tableName);
        		
        if(self::$_eventManager)
        {
        	/*
        	 * Fire "AFTER_DELETE" event for each deleted object
        	 */
	        foreach ($ids as $id)
	        {
	        	$specialCase->setId($id);
	        	self::$_eventManager->fireEvent(Db_Object_Event_Manager::AFTER_DELETE, $specialCase); 
	        }	
        }	
        return true;	    	         
    }
}